import coin.proto.coin_market_enums_pb2 as coin_enum
from coin.exchange.base.order_gateway_logger import OrderGatewayLogProvider
from coin.exchange.base.order_gateway_log_builder import (prepare_builder,
                                                          to_proto_order_side,
                                                          BaseOrderEventBuilder,
                                                          BaseRequestBuilder,
                                                          BaseResponseBuilder)
from coin.proto.coin_order_gateway_pb2 import (OrderAmendRequestProto,
                                               OrderAmendResponseProto,
                                               OrderEvent,
                                               OrderSubmitRequestProto,
                                               OrderSubmitResponseProto)
import coin.proto.coin_order_enums_pb2 as coin_order
from coin.exchange.bitmex.kr_rest.futures_product import BitmexFuturesProduct


class OrderEventBuilder(BaseOrderEventBuilder):
  def set_order(self, data):
    internal_order_id = data['clOrdID']
    external_order_id = data['orderID']
    order_price = float(data['price'])
    order_qty = float(data['orderQty'])
    if data['side'] == 'Buy':
      order_side = coin_order.BUY_ORDER
    elif data['side'] == 'Sell':
      order_side = coin_order.SELL_ORDER
    else:
      raise ValueError(data['side'])
    order_type = coin_order.DAY_LIMIT_ORDER

    self.oe.MergeFrom(
        OrderEvent(
            internal_order_id=internal_order_id,
            external_order_id=external_order_id,
            order_price=order_price,
            order_qty=order_qty,
            order_side=order_side,
            order_type=order_type,
        ))

  def set_fill(self, data):
    fill_price = float(data['lastPx'])
    fill_qty = float(data['lastQty'])

    self.oe.MergeFrom(OrderEvent(fill_price=fill_price, fill_qty=fill_qty))


class BitmexOrderGatewayLogProvider(OrderGatewayLogProvider):
  exchange_type = coin_enum.Bitmex

  def gen_order_event_and_log(self, data, timestamp, order_event_type=None):
    if not order_event_type:
      if data['ordStatus'] == 'Filled':
        # Already taken care of, skip.
        return
      elif data['ordStatus'] == 'Canceled':
        order_event_type = OrderEvent.CANCEL_CONFIRMED
      elif data['ordStatus'] == 'Rejected':
        order_event_type = OrderEvent.ORDER_REJECTED
      else:
        raise ValueError(data['ordStatus'])

    product = BitmexFuturesProduct.FromStrNativeProduct(data['symbol'])

    builder = prepare_builder(OrderEventBuilder,
                              None,
                              timestamp,
                              self.get_next_order_event_id(),
                              self.exchange_type,
                              product.symbol)
    builder.set_order(data)

    if order_event_type in [OrderEvent.ORDER_ACCEPTED, OrderEvent.ORDER_AMEND_ACCEPTED]:
      order_state = coin_order.WORKING_ORDER
    elif order_event_type == OrderEvent.ORDER_FILLED:
      order_state = coin_order.WORKING_ORDER if data['workingIndicator'] else coin_order.DEAD_ORDER
      builder.set_fill(data)
    elif order_event_type in [OrderEvent.ORDER_REJECTED, OrderEvent.CANCEL_CONFIRMED]:
      order_state = coin_order.DEAD_ORDER
    else:
      raise NotImplementedError()

    order_event_source = OrderEvent.EXCHANGE
    builder.set_state(order_event_type=order_event_type,
                      order_state=order_state,
                      order_event_source=order_event_source)
    self.write_order_event(builder.oe)

  def gen_order_request_and_order_event_and_log(self,
                                                symbol,
                                                order_price,
                                                order_qty,
                                                order_side,
                                                internal_order_id,
                                                timestamp,
                                                is_amend=False):
    if not is_amend:
      RequestProtoType = OrderSubmitRequestProto
      order_event_type = OrderEvent.ORDER_SUBMITTED
    else:
      RequestProtoType = OrderAmendRequestProto
      order_event_type = OrderEvent.ORDER_AMENDED

    req_builder = prepare_builder(BaseRequestBuilder,
                                  RequestProtoType,
                                  timestamp,
                                  self.get_next_request_id(),
                                  self.exchange_type,
                                  symbol)
    req_builder.request.internal_order_id = internal_order_id
    if not is_amend:
      self.write_order_submit_request(req_builder.request)
    else:
      self.write_order_amend_request(req_builder.request)

    oe_builder = prepare_builder(OrderEventBuilder,
                                 None,
                                 timestamp,
                                 self.get_next_order_event_id(),
                                 self.exchange_type,
                                 symbol)

    order_side = to_proto_order_side(order_side)
    order_type = coin_order.DAY_LIMIT_ORDER

    oe_builder.set_order_info(internal_order_id, order_price, order_qty, order_side, order_type)
    oe_builder.set_state(order_event_type=order_event_type,
                         order_state=coin_order.PENDING_ORDER,
                         order_event_source=OrderEvent.USER)
    self.write_order_event(oe_builder.oe)

  def gen_order_response_and_order_event_and_log(self,
                                                 symbol,
                                                 success,
                                                 internal_order_id,
                                                 timestamp,
                                                 is_amend=False):
    if not is_amend:
      ResponseProtoType = OrderSubmitResponseProto
    else:
      ResponseProtoType = OrderAmendResponseProto

    resp_builder = prepare_builder(BaseResponseBuilder,
                                   ResponseProtoType,
                                   timestamp,
                                   self.get_next_response_id(),
                                   self.exchange_type,
                                   symbol)

    resp_builder.response.success = success
    resp_builder.response.internal_order_id = internal_order_id

    if not is_amend:
      self.write_order_submit_response(resp_builder.response)
    else:
      self.write_order_amend_response(resp_builder.response)

    if not success:
      oe_builder = prepare_builder(OrderEventBuilder,
                                   None,
                                   timestamp,
                                   self.get_next_order_event_id(),
                                   self.exchange_type,
                                   symbol)
      oe_builder.oe.internal_order_id = internal_order_id

      oe_builder.set_state(order_event_type=OrderEvent.ORDER_REJECTED,
                           order_state=coin_order.DEAD_ORDER,
                           order_event_source=OrderEvent.EXCHANGE)
      self.write_order_event(oe_builder.oe)
